package com.elitesland.org.service.impl;

import cn.hutool.core.util.StrUtil;
import com.elitesland.core.base.PagingVO;
import com.elitesland.core.exception.BusinessException;
import com.elitesland.org.convert.EmployeeConvert;
import com.elitesland.org.entity.EmployeeDO;
import com.elitesland.org.entity.EmployeeTagRefDO;
import com.elitesland.org.entity.OrgBuEmployeeDO;
import com.elitesland.org.entity.QEmployeeDO;
import com.elitesland.org.param.EmployeeQParam;
import com.elitesland.org.param.EmployeeSave;
import com.elitesland.org.repo.EmployeeRepo;
import com.elitesland.org.repo.EmployeeTagRefRepo;
import com.elitesland.org.service.*;
import com.elitesland.org.vo.EmployeeVO;
import com.elitesland.org.vo.OrgBuVO;
import com.elitesland.system.param.SysUserNewParam;
import com.elitesland.system.service.SysUserService;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author mir
 */
@Service
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeRepo employeeRepo;

    private SysUserService sysUserService;

    private OrgOuService orgOuService;

    private OrgBuService orgBuService;

    private OrgBuEmployeeService orgBuEmployeeService;

    private EmployeeTagRefRepo employeeTagRefRepo;

    private OrgPostService orgPostService;

    @Autowired
    public void setEmployeeRepo(EmployeeRepo employeeRepo) {
        this.employeeRepo = employeeRepo;
    }

    @Autowired
    public void setSysUserService(SysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    @Autowired
    public void setOrgOuService(OrgOuService orgOuService) {
        this.orgOuService = orgOuService;
    }

    @Autowired
    public void setOrgBuService(OrgBuService orgBuService) {
        this.orgBuService = orgBuService;
    }

    @Autowired
    public void setOrgBuEmployeeService(OrgBuEmployeeService orgBuEmployeeService) {
        this.orgBuEmployeeService = orgBuEmployeeService;
    }

    @Autowired
    public void setEmployeeTagRefRepo(EmployeeTagRefRepo employeeTagRefRepo) {
        this.employeeTagRefRepo = employeeTagRefRepo;
    }

    @Autowired
    public void setOrgPostService(OrgPostService orgPostService) {
        this.orgPostService = orgPostService;
    }

    private final QEmployeeDO employees = QEmployeeDO.employeeDO;

    @Override
    public PagingVO<EmployeeVO> search(EmployeeQParam param) {
        Predicate predicate = buildGeneralPredicate(param);
        if (param.getBuId() != null && param.getBuId() > 0L) {
            val allBus = orgBuService.listAllById(param.getBuId());
            val allBusIds = allBus.stream().map(OrgBuVO::getId).collect(Collectors.toList());
            predicate = ExpressionUtils.and(predicate, employees.buId.in(allBusIds));
        }

        val ret = employeeRepo.findAll(predicate, param.getPageRequest());

        return PagingVO.<EmployeeVO>builder()
                .total(ret.getTotalElements())
                .records(extendRefs(ret.getContent()))
                .build();
    }

    @Override
    public PagingVO<EmployeeVO> searchRefs(EmployeeQParam param) {
        Predicate predicate = buildGeneralPredicate(param);
        if (param.getBuId() != null && param.getBuId() > 0L) {
            val allBus = orgBuService.listAllById(param.getBuId());
            val allBusIds = allBus.stream().map(OrgBuVO::getId).collect(Collectors.toList());
            val orgBuEmployees = orgBuEmployeeService.listByBuIds(allBusIds);
            val employeeIds = orgBuEmployees.stream().map(OrgBuEmployeeDO::getEmployeeId)
                    .collect(Collectors.toList());
            predicate = ExpressionUtils.and(predicate, employees.id.in(employeeIds));
        }

        val ret = employeeRepo.findAll(predicate, param.getPageRequest());
        return PagingVO.<EmployeeVO>builder()
                .total(ret.getTotalElements())
                .records(extendRefs(ret.getContent()))
                .build();
    }

    private List<EmployeeVO> extendRefs(List<EmployeeDO> employees) {
        val emps = employees.stream().map(EmployeeConvert.INSTANCE::doToVO).collect(Collectors.toList());

        val employeeIds = emps.stream().map(EmployeeVO::getId).collect(Collectors.toList());

        val tagRefs = employeeTagRefRepo.findAllByEmployeeIdIn(employeeIds);

        val postIds = employees.stream().map(EmployeeDO::getPostId)
                .collect(Collectors.toList());

        val posts = orgPostService.listByIds(postIds);

        emps.forEach(e -> {
            val tags = tagRefs.stream().filter(r -> r.getEmployeeId().equals(e.getId()))
                    .map(EmployeeTagRefDO::getTagName).collect(Collectors.toList());
            e.setTags(tags)
                    .setTagName(String.join(",", tags));

            posts.forEach(p -> {
                if (e.getPostId() != null && e.getPostId().equals(p.getId())) {
                    e.setPostName(p.getPostName());
                    if (e.getBu() != null) {
                        e.getBu().ifPresent(bu -> {
                            if (bu.getPostFactor() != null && p.getPostWeight() != null) {
                                e.setPostWeight(bu.getPostFactor() * p.getPostWeight());
                            }
                        });
                    }
                }
            });
        });

        return emps;
    }

    private Predicate buildGeneralPredicate(EmployeeQParam param) {
        Predicate predicate = employees.isNull().or(employees.isNotNull());
        predicate = param.getBuId() == null || param.getBuId() <= 0L ? predicate :
                ExpressionUtils.and(predicate, employees.buId.eq(param.getBuId()));
        predicate = param.getEmpNo() == null || param.getEmpNo().isBlank() ? predicate :
                ExpressionUtils.and(predicate, employees.empNo.like("%" + param.getEmpNo() + "%"));
        predicate = param.getName() == null || param.getName().isBlank() ? predicate :
                ExpressionUtils.and(predicate, employees.name.like("%" + param.getName() + "%"));
        predicate = param.getForeignName() == null || param.getForeignName().isBlank() ? predicate :
                ExpressionUtils.and(predicate, employees.foreignName.like("%" + param.getForeignName() + "%"));
        predicate = param.getSysUserName() == null || param.getSysUserName().isBlank() ? predicate :
                ExpressionUtils.and(predicate, employees.sysUserName.like("%" + param.getSysUserName() + "%"));
        return predicate;
    }

    @Override
    public Optional<EmployeeVO> oneById(Long id) {
        return employeeRepo.findById(id).map(EmployeeConvert.INSTANCE::doToVO);
    }

    @Override
    public List<EmployeeVO> listByIds(List<Long> ids) {
        return employeeRepo.findAllByIdIn(ids).stream().map(EmployeeConvert.INSTANCE::doToVO)
                .collect(Collectors.toList());
    }

    @Override
    public Optional<EmployeeVO> oneBySysUserName(String sysUserName) {
        return employeeRepo.findBySysUserName(sysUserName).map(e -> {
            val employeeVO = EmployeeConvert.INSTANCE.doToVO(e);

            if (e.getOuId() != null) {
                employeeVO.setOu(orgOuService.oneById(employeeVO.getOuId()));
            }
            if (e.getBuId() != null) {
                employeeVO.setBu(orgBuService.oneById(employeeVO.getBuId()));
            }
            if (e.getPostId() != null) {
                orgPostService.oneById(e.getPostId()).ifPresent(p -> {
                    employeeVO.setPostName(p.getPostName());
                    employeeVO.getBu().ifPresent(bu -> {
                        if (bu.getPostFactor() != null && p.getPostWeight() != null) {
                            employeeVO.setPostWeight(bu.getPostFactor() * p.getPostWeight());
                        }
                    });
                });
            }

            val tagRefs = employeeTagRefRepo.findAllByEmployeeId(e.getId());
            if (!tagRefs.isEmpty()) {
                employeeVO.setTags(
                        tagRefs.stream().map(EmployeeTagRefDO::getTagName).collect(Collectors.toList())
                );
                employeeVO.setTagName(String.join(",", employeeVO.getTags()));
            }

            return employeeVO;
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public Long create(EmployeeSave employee) {
        val buOpt = orgBuService.oneById(employee.getBuId());
        if (buOpt.isEmpty()) {
            throw new BusinessException("部门ID为：" + employee.getBuId() + ", 的部门不存在，无法创建员工信息");
        }
        employeeRepo.findByEmpNo(employee.getEmpNo()).ifPresent(e -> {
            throw new BusinessException("雇员编号：" + employee.getEmpNo() + ", 已被使用");
        });
        if (employee.getNeedCreateUser()) {
            val user = new SysUserNewParam()
                    .setUsername(employee.getSysUserName())
                    .setPassword(employee.getSysPassword())
                    .setFirstName(employee.getName())
                    .setLastName(employee.getForeignName())
                    .setMobile(employee.getMobile())
                    .setEmail(employee.getEmail())
                    .setEnabled(true);
            val userId = sysUserService.create(user);
            employee.setSysUserId(userId)
                    .setOuId(buOpt.get().getOuId())
                    .setBuId(buOpt.get().getId());
        }
        /***
         * 新建员工创建账号直接启用并且加上开关
         *
         * **/

        val emp = employeeRepo.save(EmployeeConvert.INSTANCE.saveToDO(employee));

        return emp.getId();
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void update(EmployeeSave employee) {
        employeeRepo.findByIdNotAndEmpNo(employee.getId(), employee.getEmpNo()).ifPresent(e -> {
            throw new BusinessException("雇员编号：" + employee.getEmpNo() + ", 已被使用");
        });
        employeeRepo.findById(employee.getId()).ifPresent(e -> {
            val buOpt = orgBuService.oneById(e.getBuId());
            if (buOpt.isEmpty()) {
                throw new BusinessException("部门ID为：" + employee.getBuId() + ", 的部门不存在，无法创建员工信息");
            }
            e.setEmpNo(employee.getEmpNo())
                    .setName(employee.getName())
                    .setMobile(employee.getMobile())
                    .setEmpNo(employee.getEmpNo())
                    .setForeignName(employee.getForeignName())
                    .setEmail(employee.getEmail())
                    .setBuId(buOpt.get().getId())
                    .setOuId(buOpt.get().getOuId());
            employeeRepo.save(e);
        });
    }

    @Override
    public void removeById(Long id) {
        employeeRepo.deleteById(id);
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void bindToBu(Long employeeId, Long buId) {
        employeeRepo.findById(employeeId).ifPresent(e -> {
            e.setBuId(buId);
            employeeRepo.save(e);
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void bindSysUserToEmployee(String sysUsername, Long employeeId) {
        sysUserService.getUserByUsername(sysUsername).ifPresentOrElse(u -> {
            employeeRepo.findById(employeeId).ifPresentOrElse(e -> {
                if (StringUtils.isNoneBlank(e.getSysUserName()) || (e.getSysUserId() != null && e.getSysUserId() > 0L)) {
                    throw new BusinessException("员工：" + e.getName() + ", 已经绑定账号：" + sysUsername);
                }
                e.setSysUserId(u.getId())
                        .setSysUserName(sysUsername);
                employeeRepo.save(e);
            }, () -> {
                throw new BusinessException("未找到编号为：" + employeeId + ", 的员工记录");
            });
        }, () -> {
            throw new BusinessException("未找到：" + sysUsername + ", 的账号记录");
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void unbindEmployeeWithSysUser(Long employeeId) {
        employeeRepo.findById(employeeId).ifPresentOrElse(e -> {
            e.setSysUserId(null)
                    .setSysUserName(null);
            employeeRepo.save(e);
        }, () -> {
            throw new BusinessException("未找到编号为：" + employeeId + ", 的员工记录");
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void bindReportTo(Long downId, Long upId) {
        oneById(downId).ifPresentOrElse(down -> oneById(upId).ifPresentOrElse(up -> {
            if (StrUtil.isBlank(up.getReportToPath())) {
                up.setReportToPath("/");
            }
            if (StrUtil.isNotBlank(down.getReportToPath()) && !StrUtil.equals(down.getReportToPath(), "/")) {
                val downPath = down.getReportToPath() + down.getId() + "/";
                val subs = employeeRepo.findAllByReportToPathLike(downPath + "%");
                if (!subs.isEmpty()) {
                    val newDownPath = StrUtil.subPre(up.getReportToPath(), up.getReportToPath().length() - 1) + downPath;
                    subs.forEach(s -> {
                        s.setReportToPath(newDownPath);
                    });
                    employeeRepo.saveAll(subs);
                }
            }
            down.setReportToId(up.getId())
                    .setReportToPath(up.getReportToPath() + up.getId() + "/");
            employeeRepo.save(EmployeeConvert.INSTANCE.voToDO(down));
        }, () -> {
            throw new BusinessException("没有找到上级员工信息");
        }), () -> {
            throw new BusinessException("没有找到下级员工信息");
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void unbindReportTo(Long id) {
        oneById(id).ifPresentOrElse(e -> {
            if (StrUtil.isNotBlank(e.getReportToPath()) && !StrUtil.equals(e.getReportToPath(), "/")) {
                val subs = employeeRepo.findAllByReportToPathLike(e.getReportToPath() + "%");
                if (!subs.isEmpty()) {
                    subs.forEach(s -> s.setReportToPath(s.getReportToPath().replace(e.getReportToPath(), "/")));
                    employeeRepo.saveAll(subs);
                }
            }
            e.setReportToId(null)
                    .setReportToPath("/");
            employeeRepo.save(EmployeeConvert.INSTANCE.voToDO(e));
        }, () -> {
            throw new BusinessException("没有找到当前员工信息");
        });
    }

    @Override
    public Optional<EmployeeVO> findReportTo(Long id) {
        return oneById(id).flatMap(e -> {
            if (e.getReportToId() == null) {
                return Optional.empty();
            } else {
                return oneById(e.getReportToId());
            }
        });
    }

    @Override
    public List<EmployeeVO> listReportSubs(Long id) {
        return employeeRepo.findAllByReportToId(id).stream().map(EmployeeConvert.INSTANCE::doToVO)
                .collect(Collectors.toList());
    }

    @Override
    public List<EmployeeVO> listReportSubsAll(Long id) {
        val upOpt = oneById(id);
        if (upOpt.isEmpty()) {
            throw new BusinessException("没有找到当前员工信息");
        }
        return employeeRepo.findAllByReportToPathLike(upOpt.get().getReportToPath() + upOpt.get().getId() + "/%")
                .stream().map(EmployeeConvert.INSTANCE::doToVO).collect(Collectors.toList());
    }
}
